import gc
import pickle
import traceback
from abc import ABCMeta
from pathlib import Path
from typing import Optional, Any, Tuple, List, Set, Union, Dict

from app.core.config import settings
from app.core.context import Context, ArchiveInfo
from app.core.event import EventManager
from app.core.module import ModuleManager
from app.db.message_oper import MessageOper
from app.helper.message import MessageHelper
from app.log import logger
from app.schemas import EventType, Notification
from app.utils.object import ObjectUtils


class ChainBase(metaclass=ABCMeta):
    """
    处理链基类
    """

    def __init__(self):
        """
        公共初始化
        """
        self.modulemanager = ModuleManager()
        self.eventmanager = EventManager()
        self.messageoper = MessageOper()
        self.messagehelper = MessageHelper()

    @staticmethod
    def load_cache(filename: str) -> Any:
        """
        从本地加载缓存
        """
        cache_path = settings.TEMP_PATH / filename
        if cache_path.exists():
            try:
                with open(cache_path, 'rb') as f:
                    return pickle.load(f)
            except Exception as err:
                logger.error(f"加载缓存 {filename} 出错：{str(err)}")
        return None

    @staticmethod
    def save_cache(cache: Any, filename: str) -> None:
        """
        保存缓存到本地
        """
        try:
            with open(settings.TEMP_PATH / filename, 'wb') as f:
                pickle.dump(cache, f)
        except Exception as err:
            logger.error(f"保存缓存 {filename} 出错：{str(err)}")
        finally:
            # 主动资源回收
            del cache
            gc.collect()

    @staticmethod
    def remove_cache(filename: str) -> None:
        """
        删除本地缓存
        """
        cache_path = settings.TEMP_PATH / filename
        if cache_path.exists():
            Path(cache_path).unlink()

    def run_module(self, method: str, *args, **kwargs) -> Any:
        """
        运行包含该方法的所有模块，然后返回结果
        当kwargs包含命名参数raise_exception时，如模块方法抛出异常且raise_exception为True，则同步抛出异常
        """

        def is_result_empty(ret):
            """
            判断结果是否为空
            """
            if isinstance(ret, tuple):
                return all(value is None for value in ret)
            else:
                return result is None

        logger.debug(f"请求模块执行：{method} ...")
        result = None
        modules = self.modulemanager.get_running_modules(method)
        for module in modules:
            module_id = module.__class__.__name__
            try:
                module_name = module.get_name()
            except Exception as err:
                logger.error(f"获取模块名称出错：{str(err)}")
                module_name = module_id
            try:
                func = getattr(module, method)
                if is_result_empty(result):
                    # 返回None，第一次执行或者需继续执行下一模块
                    result = func(*args, **kwargs)
                elif ObjectUtils.check_signature(func, result):
                    # 返回结果与方法签名一致，将结果传入（不能多个模块同时运行的需要通过开关控制）
                    result = func(result)
                elif isinstance(result, list):
                    # 返回为列表，有多个模块运行结果时进行合并（不能多个模块同时运行的需要通过开关控制）
                    temp = func(*args, **kwargs)
                    if isinstance(temp, list):
                        result.extend(temp)
                else:
                    # 中止继续执行
                    break
            except Exception as err:
                if kwargs.get("raise_exception"):
                    raise
                logger.error(
                    f"运行模块 {module_id}.{method} 出错：{str(err)}\n{traceback.format_exc()}")
                self.messagehelper.put(title=f"{module_name}发生了错误",
                                       message=str(err),
                                       role="system")
                self.eventmanager.send_event(
                    EventType.SystemError,
                    {
                        "type": "module",
                        "module_id": module_id,
                        "module_name": module_name,
                        "module_method": method,
                        "error": str(err),
                        "traceback": traceback.format_exc()
                    }
                )
        return result

    def recognize_archive(self,
                          ehid: int = None,
                          pixivid: str = None,
                          cache: bool = True) -> Optional[ArchiveInfo]:
        """
        识别媒体信息
        :param ehid:   tmdbid
        :param pixivid: 豆瓣ID
        :param cache:    是否使用缓存
        :return: 识别的媒体信息，包括剧集信息
        """
        # 识别用名中含指定信息情形

        return self.run_module("recognize_archive", ehid=ehid, pixivid=pixivid, cache=cache)

    def archive_exists(self, mediainfo: ArchiveInfo, itemid: str = None) -> Optional[ArchiveInfo]:
        """
        判断媒体文件是否存在
        :param mediainfo:  识别的媒体信息
        :param itemid:  媒体服务器ItemID
        :return: 如不存在返回None，存在时返回信息，包括每季已存在所有集{type: movie/tv, seasons: {season: [episodes]}}
        """
        return self.run_module("media_exists", mediainfo=mediainfo, itemid=itemid)

    def post_message(self, message: Notification) -> None:
        """
        发送消息
        :param message:  消息体
        :return: 成功或失败
        """
        logger.info(f"发送消息：channel={message.channel}，"
                    f"title={message.title}, "
                    f"text={message.text}，"
                    f"userid={message.userid}")
        # 发送事件
        self.eventmanager.send_event(etype=EventType.NoticeMessage,
                                     data={
                                         "channel": message.channel,
                                         "type": message.mtype,
                                         "title": message.title,
                                         "text": message.text,
                                         "image": message.image,
                                         "userid": message.userid,
                                     })
        # 保存消息
        self.messagehelper.put(message, role="user")
        self.messageoper.add(channel=message.channel, mtype=message.mtype,
                             title=message.title, text=message.text,
                             image=message.image, link=message.link,
                             userid=message.userid, action=1)
        # 发送
        self.run_module("post_message", message=message)

    def post_medias_message(self, message: Notification, medias: List[ArchiveInfo]) -> Optional[bool]:
        """
        发送媒体信息选择列表
        :param message:  消息体
        :param medias:  媒体列表
        :return: 成功或失败
        """
        note_list = [media.to_dict() for media in medias]
        self.messagehelper.put(message, role="user", note=note_list)
        self.messageoper.add(channel=message.channel, mtype=message.mtype,
                             title=message.title, text=message.text,
                             image=message.image, link=message.link,
                             userid=message.userid, action=1,
                             note=note_list)
        return self.run_module("post_medias_message", message=message, medias=medias)

    def register_commands(self, commands: Dict[str, dict]) -> None:
        """
        注册菜单命令
        """
        self.run_module("register_commands", commands=commands)

    def scheduler_job(self) -> None:
        """
        定时任务，每10分钟调用一次，模块实现该接口以实现定时服务
        """
        self.run_module("scheduler_job")

    def clear_cache(self) -> None:
        """
        清理缓存，模块实现该接口响应清理缓存事件
        """
        self.run_module("clear_cache")
